/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package it.txt.access.capability.finder;

import it.txt.access.capability.commons.filter.CapabilityFileNameFilter;
import java.io.File;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import javax.xml.xquery.XQConnection;
import javax.xml.xquery.XQDataSource;
import javax.xml.xquery.XQException;
import javax.xml.xquery.XQPreparedExpression;
import javax.xml.xquery.XQResultSequence;
import org.w3c.dom.Element;

/**
 *
 * @author seccia
 */
public class CapabilityXQuerySaxSearch {

    private static final Logger LOGGER = Logger.getLogger(CapabilityXQuerySaxSearch.class.getName());
    
    private static final String DRIVER_SAX = "net.sf.saxon.xqj.SaxonXQDataSource";

    private static final String REGEX_CHARS = "[a-zA-Z0-9_\\-\\s]+";

    private static final String REGEX_WILD = "[\\*#]{1}";

    private static final String REGEX_DOT = "\\.";

    //The regular expression used to match the resourceID
    private static final String REGEX_RESOURCE_ID = "^("
            + "(" + REGEX_CHARS + "|" + REGEX_WILD + ")"
            + "(" + REGEX_DOT + "(" + REGEX_CHARS + "|" + REGEX_WILD + "))*"
            + ")$";
    
    //This part of query is used to declare the capability namespace and
    //also the query's variables
    private static final String DECLARATION = ""
            + "declare namespace tns = 'http://schema.capability.access.txt.it';"
            + "declare variable $capabilityDoc as xs:string external;"
            + "declare variable $permittedAction as xs:string external;"
            + "declare variable $resourceIDReg as xs:string external;"            
            + "declare variable $subjectID as xs:string external;";

    //This part of query is used to retrieve the correct cabability based
    //on the requested parameters.
    private static final String QUERY = ""
            + " let $c := doc($capabilityDoc)/tns:AccessRightsCapability"
            + " let $s := $c/tns:Subject/tns:SubjectID"
            + " let $s := fn:normalize-space($s)"
            + " let $s := fn:lower-case($s)"
            + " let $r := $c/tns:ResourceID"
            + " for $p in $c/tns:AccessRights/tns:AccessRight/tns:PermittedAction"
            + " let $p := fn:normalize-space($p)"
            + " let $p := fn:lower-case($p)"
            + " where $s eq $subjectID"
            + " and $p eq $permittedAction"
            + " and fn:matches($r, $resourceIDReg, 'xi')"
            + " return $c";
    
    //The directory containing the xml capabilities
    private File capabilitiesDir;

    //The xqdata source connection
    private XQConnection xQConnection;

    //Connection enstablished
    private boolean connected;

    public CapabilityXQuerySaxSearch(File capabilitiesDir) {
        if (!capabilitiesDir.isDirectory()) {
            throw new IllegalArgumentException("No Capabilities Directory specified.");
        }
        try {
            xQConnection = ((XQDataSource) Class.forName(DRIVER_SAX).newInstance()).getConnection();
            this.connected = (xQConnection != null) ? true : false;
            this.capabilitiesDir = capabilitiesDir;
        } catch (Exception ex) {
            this.connected = false;
            LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
            // free all resources allocated for the connection
            if (xQConnection != null) {
                try {
                    xQConnection.close();
                } catch (XQException xqe) {
                    LOGGER.log(Level.SEVERE, xqe.getMessage(), xqe);
                }
            }
        }
    }

    public ArrayList<CapabilitySearchReturn> doSearchCapability(String subject, String permittedAction) {

        ArrayList<CapabilitySearchReturn> result = new ArrayList<CapabilitySearchReturn>();
        XQPreparedExpression xqpe = null;
        XQResultSequence xqrs = null;

        String userSubject = subject.trim().toLowerCase();        
        String userPermittedAction = permittedAction.trim().toLowerCase();
        
        try {

            //Prepare the query expression
            xqpe = xQConnection.prepareExpression(DECLARATION + QUERY);

            QName[] names = xqpe.getAllExternalVariables();
            for (QName qName : names) {
                LOGGER.log(Level.INFO, "Qname variable: {0}", qName.toString());
            }

            //Prepare the user request variables
            QName subjectIDVar = new QName("subjectID");
            xqpe.bindString(subjectIDVar, userSubject, null);
            QName permitedActionVar = new QName("permittedAction");
            xqpe.bindString(permitedActionVar, userPermittedAction, null);
            QName resourceIDRegVar = new QName("resourceIDReg");
            xqpe.bindString(resourceIDRegVar, REGEX_RESOURCE_ID, null);

            for (String fileName : capabilitiesDir.list(new CapabilityFileNameFilter())) {

                File capabilityFile = new File(capabilitiesDir, fileName);

                LOGGER.log(Level.INFO, "Searching in file: {0}", capabilityFile.getAbsolutePath());

                //bind the current document
                QName documentVar = new QName("capabilityDoc");
                xqpe.bindString(documentVar, capabilityFile.toURI().toString(), null);

                // execute the XQuery expression
                xqrs = xqpe.executeQuery();

                // process the result (sequence) iteratively
                while (xqrs.next()) {
                    // retrieve the current capability
                    Element capability = (Element) xqrs.getObject();
                    String capabilityID = capability.getAttribute("AccessRightsCapabilityID");
                    String capabilityIssueIstant = capability.getAttribute("IssueInstant");
                    CapabilitySearchReturn csr = new CapabilitySearchReturn();
                    csr.setCapabilityIssueDate(capabilityIssueIstant);
                    csr.setCapabilityID(capabilityID);
                    result.add(csr);
                }
                //Close the result set
                xqrs.close();
            }
        }
        catch (XQException ex) {
            // handle the exception in some manner
            LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
        } // free the resources whether or not there was an exception
        finally {
            // free all resources allocated for the result
            if (xqrs != null && !xqrs.isClosed()) {
                try {
                    xqrs.close();
                } catch (XQException ex) {
                    LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
                }
            }
            // free all resources allocated for the expression
            if (xqpe != null) {
                try {
                    xqpe.close();
                } catch (XQException ex) {
                    LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
                }
            }
        }
        return result;
    }

    public boolean isAlive() {
        return connected;
    }

    /*public static void main(String[] args) {

        File capabilitiesDir = new File("D:/Capabilities");
        String subject = "alice@acme.com";
        String operation = "Create";
        
        CapabilityXQuerySaxSearch cs = new CapabilityXQuerySaxSearch(capabilitiesDir);

        ArrayList<CapabilitySearchReturn> capabilities = cs.doSearchCapability(subject, operation);

        Collections.sort(capabilities, new Comparator<CapabilitySearchReturn>() {

            @Override
            public int compare(CapabilitySearchReturn o1, CapabilitySearchReturn o2) {
                XMLGregorianCalendar issueDate1 = o1.getCapabilityIssueDateToXmlGregorianCalendar();
                XMLGregorianCalendar isssueDate2 = o2.getCapabilityIssueDateToXmlGregorianCalendar();
                return issueDate1.compare(isssueDate2);
            }
        });

        LOGGER.log(Level.INFO, "\nRetrieved Capabilities - Sort By Date \n");

        for (CapabilitySearchReturn capabilitySearchReturn : capabilities) {
            LOGGER.log(Level.INFO, "Capability ID: {0}", capabilitySearchReturn.getCapabilityID());
        }
    }*/
}
